<%
'######################################################################
'## ab.tpl.asp
'## -------------------------------------------------------------------
'## Feature     :   AspBox Templates Class
'## Version     :   v1.2
'##	Author		:	Taihom(http://www.taihom.com/templateclass/)
'## Update      :   Lajox(lajox@19www.com)
'## Update Date :   2011/09/05 18:51
'##	Description	:	Taihom ASP Template 4.0(2011.02.17) For AspBox (Plugin)
'######################################################################

Class Cls_AB_Tpl
	Private TplXml,AdoConn
	Private DicBlock,DicLabel
	Private s_RootPath, s_Charset, s_TagHead, s_XmlRoot, s_BlockAtr
	Private i_SetHtml,i_AbsPath
	Private s_TplPath, s_TplFilePath
	Private s_TplHtml, s_ResultHtml
	Private s_TimeInterval, s_TplPagePath, s_TplCachePath, i_TplCacheType, i_TplCacheTime
	Private s_CacheName, s_TplCacheName

	'构造函数
	Private Sub Class_Initialize()
		'常用
		s_Charset		= AB.CharSet		'编码设置
		s_TagHead		= "$"				'"tpl:"
		s_TplPath		= "/templates/"		'模板存放目录
		s_XmlRoot		= "//template"		'模板根节点名称
		s_BlockAtr		= "name"			'块赋值辅助的属性
		i_SetHtml		= 0					'默认从文件中读取html，=1表示是从setHtml中取得模板
		i_AbsPath		= 0					'输出结果是否使用绝对路径 (0不用,1用)
		
		s_TimeInterval	= "s"				'表示相隔时间的类型：d日 h时 n分钟 s秒
		i_TplCacheType	= 0					'缓存类型
		i_TplCacheTime	= 10				'缓存时间
		s_TplCachePath	= "/_Cache/_Templates/"	'缓存目录
		
		'错误调试由AspBox接管
		AB.Error(90001) = "数据库打开出错,请检查数据库连接."
		
		'设置使用到的对象
		'Fso类
		AB.Use "Fso"
		AB.Fso.CharSet = s_Charset
		
		'Xml对象
		Set TplXml = XmlDom(Right(s_XmlRoot, Len(s_XmlRoot)-2))
		
		'使用到的字典对象
		Set DicLabel  = Dicary() '储值
		Set DicBlock  = Dicary() 
		
		'设置数据库连接
		AB.Use "db"
		Set AdoConn = AB.db.Conn
	End Sub

	
	'析构函数
	Private Sub Class_Terminate()
		'注销对象
		Set TplXml		= Nothing
		Set DicLabel	= Nothing
		Set DicBlock	= Nothing
		If IsObject(AdoConn) Then Set AdoConn = Nothing
	End Sub

	
	'新建实例
	Public Function [New]()
		Set [New] = New Cls_AB_Tpl
	End Function

	
	'设置站点根目录路径
	Public Property Let setRootPath(ByVal v)
		RootPath = v
	End Property

	Public Property Let RootPath(ByVal v)
		s_RootPath = v
	End Property

	
	'设置使用字符编码
	Public Property Let setCharset(ByVal v)
		[Charset] = v
	End Property

	Public Property Let [Charset](ByVal v)
		s_Charset = v
	End Property

	
	'设置单标签头
	Public Property Let setTagHead(ByVal v)
		TagHead = Lcase(Trim(v))
	End Property

	Public Property Let TagHead(ByVal v)
		s_TagHead = Lcase(Trim(v))
	End Property

	
	'设置使用绝对路径
	Public Property Let setAbsPath(ByVal i)
		IsAbsPath = i
	End Property

	Public Property Let IsAbsPath(ByVal i)
		i_AbsPath = i
	End Property


	'设置模板存放路径
	Public Property Let setTemplatePath(ByVal v)
		TplPath = v
	End Property

	Public Property Let TplPath(ByVal v)
		s_TplPath = v
	End Property

	
	'设置模板文件路径
	Public Property Let setTemplateFile(ByVal v)
		TplFile = v
	End Property

	Public Property Let TplFile(ByVal v)
		s_TplPagePath = v
		s_TplFilePath = AB.Fso.MapPath(s_RootPath & s_TplPath & s_TplPagePath)
		s_CacheName = s_TplCacheName & "/" & AB.Fso.NameOf(s_TplPagePath)
		'设置路径后立即加载模板
		Call LoadCacheTemplate()
	End Property

	
	Public Property Let setHtml(ByVal s)
		Html = s
	End Property

	Public Property Let Html(ByVal s)
		i_SetHtml = 1'直接设置模板,缓存失效
		s_TplHtml = s
		Call loadCacheTemplate()
	End Property

		
	'参数1: 缓存的名字,每个页面不能相同
	'参数2: 0=都不缓存,1=内存缓存,2=文件缓存(缓存会缓存数据跟模板,开启缓存必须要有一个缓存名字)
	'参数3: 缓存时间，单位是默认是秒
	Public Property Let SetCache(ByVal v)
		TplCache = v
	End Property

	Public Property Let TplCache(ByVal v)
		If v = "0" Then i_TplCacheType = 0 : Exit Property
		Dim arr : arr = ExpSplit(v,"\s*,\s*")
		Select Case Ubound(arr,1)
		Case 0
			s_TplCacheName = arr(0)
		Case 1
			s_TplCacheName = arr(0)
			i_TplCacheType = CInt(arr(1))
		Case 2
			s_TplCacheName = arr(0)
			i_TplCacheType = CInt(arr(1))
			i_TplCacheTime = CInt(arr(2))
		Case 3
			s_TplCacheName = arr(0)
			i_TplCacheType = CInt(arr(1))
			i_TplCacheTime = CInt(arr(2))
			s_TplCachePath = arr(3)
		End Select
		If i_TplCacheTime <= 0 Then
			i_TplCacheType = 0
		End If
	End Property

	
	'设置数据库连接
	Public Property Let Conn(ByVal o)
		On Error Resume Next
		Set AdoConn = o
		If AdoConn.State = 0 Then AdoConn.Open()
		If Err.Number <> 0 Then
			Err.Clear : Set AdoConn = Nothing
			AB.Error.Raise 90001
		End If
		On Error Goto 0
	End Property

	
	'赋值
	Public Property Let d(ByVal s,ByVal v)
		Assign s, v
	End Property

	Public Default Sub Assign(ByVal s, ByVal v)
		Dim i,ary : ary = ExpSplit(s,"\s*,\s*")
		For i = 0 To Ubound(ary)'多标签赋值
			s = Lcase(ary(i))
			If s = s_TagHead Then
				Select Case TypeName(v)
					Case "Recordset"'记录集
						If v.State And Not v.eof Then
							Set DicLabel = RsToDic(v,DicLabel)
						End If
					Case "Dictionary"'字典
						Set DicLabel = v
					Case "Variant()"'如果传递的是数组
						If Ubound(v) = 1 Then
							Select Case TypeName(v(0))
								Case "Recordset"
									If v(0).State And Not v(0).eof Then
										Set DicLabel = RsToDic(v(0),DicLabel)
									End If
								'Case "Cls_AB_List"
								'	If v(0).Size > 0 Then
								'		Set DicLabel = RsToDic(v(0).Data)
								'	End If
								Case "Variant()"
									Set DicLabel = RsToDic(v(0),DicLabel)
							End Select
							Dim aryField : aryField = ExpSplit(v(1),"\s*,\s*")'字段序列
							If TypeName(aryField)="Variant()" Then Set DicLabel = RedimField(DicLabel,aryField)'重命名字段
						End If
				End Select
			Else'普通赋值,支持字典，普通数据(字段值、字符串、数字等)
				Select Case TypeName(v)
					Case "Dictionary","Recordset"
						Set DicLabel(s) = v
					Case "Cls_AB_List"
						If v.Size > 0 Then
							Set DicLabel(s) = RsToDic(v,DicLabel)
						End If
					Case Else
						DicLabel(s) = v
				End Select
			End If
		Next
	End Sub


	'生成静态页面(路径,页面名称)
	Public Property Let Create(ByVal param)
		SaveAs param
	End Property

	Public Function SaveAs(ByVal param)
		Dim strFilePath,strContents
		If TypeName(param) = "Variant()" Then'传递数组
			Select Case Ubound(param)
			Case 0'Array(createpath+pagename)
				strFilePath = param(0)
				strContents = GetHtml
			Case 1'Array(createpath+pagename,content)
				strFilePath = param(0)
				strContents = param(1)
			Case Else'Array(createpath+pagename,content,charset)
				strFilePath = param(0)
				strContents = param(1)
				s_Charset  = param(2)
			End Select
		Else '文件路径+文件名
			strFilePath = param
			strContents = GetHtml
		End If
		SaveAs = AB.Fso.CreateFile(strFilePath & ">" & s_Charset, strContents)
	End Function


	'设置节点属性
	Public Property Let setAttr(ByVal strPath,ByVal v)
		Attr(strPath) = v
	End Property

	Public Property Let Attr(ByVal strPath,ByVal v)
		SetLabelAttr Lcase(strPath),v
	End Property

	
	'获取节点属性
	Public Property Get getAttr(ByVal strPath)
		Attr strPath
	End Property

	Public Property Get Attr(ByVal strPath)
		Dim i,ary,node
			ary = SelectLabelNode(Lcase(strPath))'选择标签节点
		If IsArray(ary) = False Then Exit Property
		
		Select Case Lcase(ary(3))
		Case ":body"
			Set node = TplXml.selectNodes(ary(4) & "/body")
		Case ":empty",":null",":eof"
			Set node = TplXml.selectNodes(ary(4) & "/null")
		Case ":html"
			Set node = TplXml.selectNodes(ary(4) & "/html")
		Case Else
			If Len(ary(2)) Then
				Set node = TplXml.selectNodes(ary(4)&"/@"&ary(2))
			Else'如果没有属性路径就返回节点的所有属性
				Set node = TplXml.selectNodes(ary(4))
				Redim tagAttr(node.Length)
				For i = 0 to node.Length - 1
					Set tagAttr(i) = GetBlockAttr(node(i))
				Next
				Attr = tagAttr
				Exit Property
			End If
		End Select
		
		If IsObject(node) Then
			If node.Length Then 
				Redim tagAttr(node.Length)
				For i = 0 to node.Length - 1
					tagAttr(i) = node(i).nodeTypedValue
				Next

				'如果只有一个结果，就返回这个结果
				If Ubound(tagAttr) = 1 Then
					Attr = tagAttr(0)
				Else'如果有多个结果就返回数组
					Attr = tagAttr
				End If
			End If
			Set node = Nothing
		End If
	End Property

	
	'获得标签所有的值
	Public Property Get getLabelValues(ByVal v)
		If IsObject(GetLabVal(v)) Then
			Set getLabelValues = GetLabVal(v)
		Else
			getLabelValues = GetLabVal(v)
		End If
	End Property

	Public Property Get GetLabVal(ByVal v)
		If Lcase(v) = Lcase(s_TagHead) Then'如果返回所有值对象
			Set GetLabVal = DicLabel
		Else
			If IsObject(DicLabel(v)) Then
				Set GetLabVal = DicLabel(v)
			Else
				GetLabVal = DicLabel(v)
			End IF
		End If
	End Property

	
	'输出部分
	Public Property Get GetHtml
		Dim node
		Set node = TplXML.selectSingleNode(s_XmlRoot) '从根目录开始遍历模板标签节点
		
		Dim CacheName : CacheName = s_CacheName & ".html"
		Select Case i_TplCacheType
			Case 3'结果内存缓存
				CacheName = CacheName & ".app"
				AB.Cache(CacheName).Expires = i_TplCacheTime
				If AB.Cache(CacheName).Ready Then
					s_ResultHtml = AB.Cache(CacheName)
				Else
					s_ResultHtml = AnalysisTemplate(node, s_TplHtml, s_TagHead, DicLabel)
					AB.Cache(CacheName) = s_ResultHtml
					AB.Cache(CacheName).SaveApp
				End If
			Case 4'结果文件缓存
				'检查文件是否存在:缓存不存在=-1,超时>0,没有超时=0
				AB.Cache(CacheName).Expires = i_TplCacheTime
				If AB.Cache(CacheName).Ready Then
					s_ResultHtml = AB.Cache(CacheName)
				Else
					s_ResultHtml = AnalysisTemplate(node, s_TplHtml, s_TagHead, DicLabel)
					AB.Cache(CacheName) = s_ResultHtml
					AB.Cache(CacheName).Save
				End If
			Case Else
				s_ResultHtml = AnalysisTemplate(node, s_TplHtml, s_TagHead, DicLabel)
		End Select
		Set node = Nothing
		'返回执行时间
		s_ResultHtml = AB.C.RegReplace(s_ResultHtml,"\{runtime\s*\/?\}|(\<\!--runtime--\>)(.*?)\1" , "<" & "!--runtime-->" & AB.ScriptTime & "<" & "!--runtime-->" )
		GetHtml = s_ResultHtml
	End Property

	
	'输出模板部分
	Public Property Get Display
		Call Show()
	End Property

	Public Sub Show()
		AB.C.Print GetHtml
	End Sub

	
	'私有函数部分----------------------
	'字典对象
	Public Function Dicary()
	  Set Dicary = Server.CreateObject(AB.dictName)
	End Function

	
	'XmlDom对象
	Private Function XmlDom(ByVal root)
		If AB.C.IsInstall("MSXML2.DOMDocument") Then
		'msxml ver 3
			Set XmlDom = Server.CreateObject("MSXML2.DOMDocument")
		ElseIf AB.C.IsInstall("Microsoft.XMLDOM") Then
		'msxml ver 2
			Set XmlDom = Server.CreateObject("Microsoft.XMLDOM")
		End If
		'异步
		XmlDom.Async = False

		If AB.C.Has(root) Then
			'创建一个节点对象
			XmlDom.appendChild(XmlDom.CreateElement(root))
			'添加xml头部
			Dim head
			Set head = XmlDom.CreateProcessingInstruction("xml","version=""1.0"" encoding=""" & s_Charset & """")
				XmlDom.insertBefore head, XmlDom.childNodes(0)
		End If
	End Function

	
	'转义正则字符
	Private Function ExpEncode(ByVal sText)
		Dim i,ary : ary = Split(". * + ? | ( ) { } ^ $ :"," ")
		sText = Replace(sText, "\" , "\\")
		For i = 0 to Ubound(ary)
			sText = Replace(sText, ary(i) , "\"&ary(i))
		Next
		ExpEncode = sText
	End Function


	'ASP的正则ExpSplit
	Private Function ExpSplit(ByVal a,ByVal b)
		Dim Match, SplitStr : SplitStr = a
		Dim Sp : Sp = "#taihom.com@"
			For Each Match in AB.C.RegMatch(a,b)
				SplitStr = Replace(SplitStr, Match.value, Sp, 1, -1, 0)
			Next
		ExpSplit = Split(SplitStr, Sp)
	End Function

	
	'功能:返回指定数组的维数。
	Private Function GetArrayDimension(ByVal aryVal)
		On error resume next
		GetArrayDimension = -1
		If Not IsArray(aryVal) Then
			Exit Function
		Else
			Dim i,iDo
			For i = 1 To 4
				iDo = UBound(aryVal, i)
				If Err Then
					Err.Clear
					Exit Function
				Else
					GetArrayDimension = i
				End If
			Next
		End If
	End Function

	
	'加载或者缓存模板
	Private Sub LoadCacheTemplate
		'缓存类型 0=不缓存,1=内存缓存,2=文件缓存
		If i_TplCacheType = 0 Then Call load() : Exit Sub
		'处理缓存时间单位（AspBox的缓存类只支持以分为单位）
		Select Case s_TimeInterval
			Case "d" i_TplCacheTime = i_TplCacheTime*60*24
			Case "h" i_TplCacheTime = i_TplCacheTime*60
			'Case "n"
			Case "s" i_TplCacheTime = i_TplCacheTime/60
		End Select
		AB.Use "Cache"
		AB.Cache.SavePath = s_TplCachePath
		If Instr("1,3", i_TplCacheType) Then	'1=模板内存缓存,3=结果内存缓存
			Dim TplHtmlCaChe, TplXmlCache
			TplHtmlCaChe = s_CacheName & ".TplHtml.app"
			TplXmlCache = s_CacheName & ".TplXml.app"
			AB.Cache(TplHtmlCaChe).Expires = i_TplCacheTime
			AB.Cache(TplXmlCache).Expires = i_TplCacheTime
			If AB.Cache(TplHtmlCaChe).Ready Then
				s_TplHtml = AB.Cache(TplHtmlCaChe)
				Set TplXml = XmlDom("")
				TplXml.loadXML(AB.Cache(TplXmlCache))
			Else
				load()
				AB.Cache(TplHtmlCaChe) = s_TplHtml
				AB.Cache(TplXmlCache) = TplXml.xml
				AB.Cache.SaveAppAll
			End If
		ElseIf Instr("2,4", i_TplCacheType) Then	'2=模板文件缓存,4=结果文件缓存
			Dim CacheName : CacheName = s_CacheName & ".xml"
			AB.Cache(CacheName).Expires = i_TplCacheTime
			If AB.Cache(CacheName).Ready Then
				Set TplXml = XmlDom("")
				TplXml.loadXML(AB.Cache(CacheName))
				s_TplHtml = TplXML.selectSingleNode(s_XmlRoot).lastchild.data
			Else
				load()
				AB.Cache(CacheName) = TplXml.xml
				AB.Cache(CacheName).Save
			End If
		End If
		'3=结果内存缓存,'4=结果文件缓存
		'If Instr("3,4", i_TplCacheType) Then GetHtml()
	End Sub

	
	Private Sub load()	'读取模板文件
		If i_SetHtml = 0 Then	'如果是从文件中读取模板
			s_TplHtml = LoadInclude(AB.Fso.Read(s_TplFilePath),s_TplFilePath)'
		End If
		s_TplHtml = AB.C.RegReplace(AB.C.RegReplace(s_TplHtml,"\<\!\-\-\s*\{","{"),"\}\s*\-\-\>","}")
		'使用绝对路径
		s_TplHtml = AB.C.IIF(Cbool(i_AbsPath),absPath(s_TplHtml),s_TplHtml)
		'编译模板，并且用XML存储模板标签节点
		Dim XmlRoot
		Set XmlRoot = TplXML.selectSingleNode(s_XmlRoot)
		CompileTemplate Array(s_TplHtml,s_TagHead),XmlRoot
		'保存模板到XML
		XmlRoot.appendChild(TplXml.createCDATASection(s_TplHtml))
		s_TplHtml = XmlRoot.lastchild.data
	End Sub

	
	'模板的include支持
	Private Function LoadInclude(ByVal strHtml,ByVal strPath)
		Dim incPath,html : html = strHtml
		Dim Match,Matches
		For Each Match In AB.C.RegMatch(strHtml,"{include\s*([\('""])?\s*(.*?)\1}")
			incPath = AB.Fso.MapPath(s_RootPath & s_TplPath & Match.SubMatches(1))
			If strPath <> incPath Then
			html = Replace(html,Match.value,LoadInclude(AB.Fso.Read(incPath),incPath),1,-1,0)
			Else
			html = Replace(html,Match.value,"",1,-1,0)
			End If
		Next
		LoadInclude = html
	End Function

	
	'编译模板
	'参数：模板内容,标签头,XML节点路径
	Private Sub CompileTemplate(ByVal aryVal,ByVal nodeDOM)
		If Len(aryVal(1))=0 Then Exit Sub End If
		Dim Match,Matches,strPattern
		Dim arrayTags(10) '定义一个数组，把模板的标签参数保存调用
			strPattern = "\{("&ExpEncode(Lcase(aryVal(1)))&")([a-zA-Z0-9:_]+)?\s*?([\s\S]*?)\/?\}[\n|\s|\t]*?(?:[\n]*?([\s\S]*?)[\n|\s|\t]*?(\{/\1\2\}))?"
		'解析标签
		For Each Match in AB.C.RegMatch(aryVal(0),strPattern)
			arrayTags(0) = Lcase(Match.SubMatches(0)) ' 标签头
			arrayTags(1) = Lcase(Match.SubMatches(1)) ' 标签名称
			arrayTags(2) = Match.SubMatches(2) ' 标签属性
			arrayTags(3) = Match.SubMatches(3) ' 闭合部分的内容
			arrayTags(4) = ""                  ' empty标签
			arrayTags(5) = arrayTags(3)        ' 仅循环体部分,不包含empty
			arrayTags(6) = AB.C.IIF(Len(Match.SubMatches(4))+Len(Match.SubMatches(3)),1,0) ' 如果是闭合标签，并且有模板内容，闭合标签才有效
			arrayTags(7) = Match.Value '模板内容

			'如果是有结束标签,表示这个是一个闭合标签
			If arrayTags(6) Then
			Dim closeTags : closeTags = GetCloseBlock(Array(arrayTags(3),arrayTags(1)))
				arrayTags(4) = closeTags(0)    ' empty标签
				arrayTags(5) = closeTags(1)    ' 仅循环体部分,不包含empty
				arrayTags(8) = AB.C.RegReplace( GetBlockAttr(nodeDOM)("nodepath") & "/" & arrayTags(1),"^\/","")'节点路径
			End If
			'创建节点
			nodeDOM.appendChild(GetTemplateNode(arrayTags))
		Next
	End Sub

	
	'解析模板
	'模板输出的思路是，遍历模板标签节点，根据编译的节点信息来输出值
	Private Function AnalysisTemplate(ByVal node, ByVal strHtml, ByVal strHead, ByVal dic)
		Dim html : html = strHtml
			
			html = AnalysisBlockLabel(html, node.childNodes, strHead, dic)'循环以及嵌套循环标签
			
			html = ReturnLabelValues(html, strHead, dic, 1)'单标签
			
			html = ExecuteTemplate(ReturnIfLabel(html, strHead, dic))
			
		AnalysisTemplate = html
	End Function

	
	'解析标签，获取值
	'参数：代码、节点、标签前缀、字典数据(用来支持标签值的调用)
	Private Function AnalysisBlockLabel(ByVal strHtml,ByVal node,ByVal strHead,ByVal objDIC)
		If Len(strHtml) = 0 Then Exit Function End If
		'由于是从根节点开始遍历，所以不用考虑多个标签相同的情况，所以只要遍历根结点的子节点就可以了
		Dim html : html = strHtml
		Dim i
		For i = 0 To node.Length - 1
		'遍历所有子节点,遇到循环就递归调用
			If node(i).childNodes.Length > 3 Then
			
				Dim DicData : Set DicData = Dicary()
				Dim aryLabel : aryLabel = GetLabelNode(node(i))'提取节点值
				
				'获取节点路径
				If Len(aryLabel(5)(s_BlockAtr)) Then
					DicData("path") = aryLabel(5)("nodepath") & "[" & s_BlockAtr & "=" & aryLabel(5)(s_BlockAtr) & "]"
				Else
					DicData("path") = aryLabel(5)("nodepath")
				End If
				'根据节点路径获得值
				If dicLabel.Exists(DicData("path")) Then'根据节点路径尝试找值
					If IsObject(dicLabel(DicData("path"))) Then
						Set DicData("data") = dicLabel(DicData("path"))
					Else
						DicData("data") = dicLabel(DicData("path"))
					End If
				Else'如果没有赋值,就根据节点设置的获得值
					Dim sql,conn,rs
						sql = aryLabel(5)("sql")
						conn= aryLabel(5)("conn")
						
						sql  = AB.C.RegReplace(sql,"^(\w+)\((?:\w+)\s*,\s*(?:\w+)\)$","$1(aryLabel(0),aryLabel(5))")'动态SQL赋值
						If Right(sql,25) = "(aryLabel(0),aryLabel(5))" Then
							sql  = Eval(sql)
						End If
						
						sql  = ReturnLabelValues(sql,strHead,objDIC,0)'获取Sql
						conn = ReturnLabelValues(conn,strHead,objDIC,0)'获取CONN

					If Len(sql) > 6 Then'如果SQL不为空
						'On error resume next
						If Len(conn) > 2 Then
							Set conn = Eval(conn)
						Else
							Set conn = adoConn
						End If
						If conn.State = 0 Then conn.Open() '如果数据库是关闭的就打开
						Set DicData("data") = conn.execute(sql)
					End If
				End If
				
				DicData("tagHead") = aryLabel(0)&"."
				
				'获得处理值,返回的数据只有两种情况,一种格式数组，一种是其他格式
				Set DicData = GetBlockData(DicData)
					'DicData("field") '如果需要重定义字段名
					'DicData("dr") '设置有dr函数
					'DicData("eof") = 1 表示没有数据
					'DicData("returndata") 返回的数据
					'DicData("data") 原来的数据
				
				'数据准备结束
				If Not DicData.Exists("dr") Then'如果没有设置渲染块
					DicData("dr") = AB.C.RegReplace(aryLabel(5)("dr"),"\s*([a-zA-Z0-9]+)\(([a-zA-Z0-9]+)\)\s*","$1(dicRS)")'数据渲染
				End If

				'数据处理
				Dim returnData : returnData = DicData("returndata")
				Dim returnHtml : returnHtml = ""
				Dim dicRS , k : k = 0
				If DicData("eof") Then'如果没有数据
					returnHtml = ReturnLabelValues(aryLabel(4),strHead,objDIC,1)
				Else'如果有数据
					Select Case TypeName(returnData)
						Case "Variant()"'统一返回数组
							For k = 0 To Ubound(returnData)
								'获取字段值
								Set dicRS = returnData(k) : dicRS("i") = k + 1
								'重命名字段
								If TypeName(DicData("field"))="Variant()" Then Set dicRS = redimField(dicRS,DicData("field"))
								'数据重定义或渲染
								If Right(DicData("dr"),7)="(dicRS)" Then Eval(DicData("dr"))
								'返回块的值
								returnHtml = returnHtml &_
								AnalysisBlockLabel(ReturnLabelValues(aryLabel(3),DicData("tagHead"),dicRS,1),node(i).childNodes,DicData("tagHead"),dicRS)'递归循环
								returnHtml = ReturnIfLabel(returnHtml,DicData("tagHead"),dicRS)'搞定IF比较值
							Next
							'returnHtml = DicData("blockHtml")
						Case Else'其他类型
							returnHtml = returnData
					End Select
				End If

				'标签替换
				html = Replace(html,aryLabel(2),returnHtml,1,-1,0)
				Set DicData = Nothing
			End If
		Next
		AnalysisBlockLabel = html
	End Function

	
	'获得块的值以及类型
	'传递数据，返回一维数据，元素一定要是字典数据否则不能处理
	Private Function GetBlockData(ByVal DicData)
		'检测块值的类型
		Dim aryTemp,aryData,dic,rs,returnData
		Dim recIndex,fldIndex
		Select Case TypeName(DicData("data"))
			Case "Recordset"'如果块传值是记录集
				Set rs = DicData("data")
				If rs.Eof Then
					DicData("eof") = 1
				Else
					aryTemp = rs.getRows()
					ReDim aryData(Ubound(aryTemp,2),Ubound(aryTemp,1))
					ReDim returnData(Ubound(aryTemp,2))
					
					For recIndex = 0 To UBound(aryTemp,2)'行
						Set dic = Dicary()
						For fldIndex = 0 To UBound(aryTemp)'字段
							'aryData(recIndex,fldIndex) = aryTemp(fldIndex,recIndex)&""
							dic(Lcase(rs.Fields(fldIndex).Name)) = aryTemp(fldIndex,recIndex)&""
							dic(fldIndex) = aryTemp(fldIndex,recIndex)&""
						Next
						Set returnData(recIndex) = dic'返回数组
					Next
				End If
			Case "Variant()"'如果传递的是数组
				If Ubound(DicData("data")) = 1 Then
					DicData("field") = ExpSplit(DicData("data")(1),"\s*,\s*")'字段序列
					Select Case TypeName(DicData("data")(0))
					Case "Recordset"'数据集
						Set rs  = DicData("data")(0)
						If rs.Eof Then
							DicData("eof") = 1
						Else
							aryTemp = rs.getRows
							ReDim aryData(Ubound(aryTemp,2),Ubound(aryTemp))
							ReDim returnData(Ubound(aryTemp,2))
							For recIndex = 0 To UBound(aryTemp,2)'行
								Set dic = Dicary()
								For fldIndex = 0 To UBound(aryTemp)'字段
									'aryData(recIndex,fldIndex) = aryTemp(fldIndex,recIndex)&""
									dic(Lcase(rs.Fields(fldIndex).Name)) = aryTemp(fldIndex,recIndex) & ""
									dic(fldIndex) = aryTemp(fldIndex,recIndex) & ""
								Next
								Set returnData(recIndex) = dic'返回数组
							Next
						End If
					Case "Variant()", "Cls_AB_List"	'数组,AspBox超级数组
						If TypeName(DicData("data")(0)) = "Cls_AB_List" Then
							aryTemp  = DicData("data")(0).Data
						Else
							aryTemp  = DicData("data")(0)
						End If
						Dim arycount : arycount = GetArrayDimension(aryTemp)
						If arycount = 1 Then'如果是一维数组
							If Ubound(aryTemp) = 0 Then
								DicData("eof") = 1
							Else
								ReDim returnData(0)
								Set dic = Dicary()
								For fldIndex = 0 To UBound(aryTemp) '字段
									dic(fldIndex) = aryTemp(fldIndex) & ""
								Next
								Set returnData(0) = dic
							End If
						ElseIf arycount = 2 Then'二维数组
							If Ubound(aryTemp,1)=0 Then
								DicData("eof") = 1
							Else
								ReDim returnData(Ubound(aryTemp,1))
								For recIndex = 0 To Ubound(aryTemp,1)
									Set dic = Dicary()
									For fldIndex = 0 To Ubound(aryTemp,2)'二级循环数据赋值
										dic(fldIndex) = aryTemp(recIndex,fldIndex) & ""'字段下标
									Next
									Set returnData(recIndex) = dic'返回数组
								Next
							End If
						Else
							returnData  = Null
						End If
					Case Else'
						returnData = aryTemp
					End Select
				End If
			Case "Dictionary"'如果传递的是字典
				If DicData("data").Count = 0 Then
					DicData("eof") = 1
				Else
					ReDim returnData(0)
					Set returnData(0) = DicData("data")
				End If
			Case Else'其他数据类型，主要是 字符、数字等可以直接输出的类型
				returnData = DicData("data")
		End Select
		'设置返回值函数
		DicData("returndata") = returnData
		
		Set GetBlockData = DicData
	End Function

	
	'格式化值输出,参数：值，属性
	Private Function FormatValues(ByVal v,ByVal dicAttr)
		Dim return : return = v
		Dim key,val
		For Each key In dicAttr''遍历节点属性节点,根据节点的属性返回值
			val = ReturnLabelValues(dicAttr(key),s_TagHead,DicLabel,0)
			Select Case (Lcase(key))
			Case "dateformat","datetime":'日期格式化
				return = AB.C.DateTime(return,val)
			Case "len","length"
				return = AB.C.IIF(Len(val),Left(return,val),return)
			Case "cutstr"
				return = AB.C.CutStr(return,val)
			Case "return"
				Dim str,i : val = Split(Lcase(val),",")
				For i=0 To Ubound(val)
					Select Case val(i)
					Case "urlencode":'返回URLEncode
						return = Server.URLEncode(return)
					Case "htmlencode":
						return = Server.HTMLEncode(return)
					Case "htmldecode":
						return = AB.C.HtmlDecode(return)
					Case "clearhtml","removehtml":'清除html格式
						return = AB.C.HtmlFilter(return)
					Case "clearspace":
						return = AB.C.RegReplace(return,"[\n\t\r|]|(\s+|&nbsp;|　)+", "")
					Case "clearformat":'清除所有格式
						return = AB.C.RegReplace(return,"<[^>]*>|[\n\t\r|]|(\s+|&nbsp;|　)+", "")
					End Select
					str = str & return
				Next
				return = str
			End Select
		Next 
		FormatValues = return
	End Function

	
	'重定义字段数据
	Private Function RedimField(ByVal DicData,aryField)
		Dim i
		For i = 0 To Ubound(aryField)
			If DicData.Exists(i) Then DicData(Lcase(aryField(i))) = DicData(i)
		Next
		Set RedimField = DicData
	End Function

	
	'记录集转为字典
	Private Function RsToDic(ByVal data, ByVal dic)
		Dim i
		Select Case TypeName(data)
			Case "Recordset"'数据集
				For i = 0 To data.Fields.Count-1 '字段序列
					dic(Lcase(data.Fields(i).Name)) = data(i) & ""'字段名
					dic(i) = data(i) & ""'字段下标
				Next
			Case "Cls_AB_List"'AspBox超级数组
				Dim Maps : Set Maps = data.Maps
				For i = 0 To data.End
					dic(Lcase(Maps(i))) = data(i) & ""
					dic(i) = data(i) & ""
				Next
			Case "Variant()"'数组
				For i = 0 To Ubound(data) '字段序列
					dic(i) = data(i) & ""'字段下标
				Next
		End Select
		Set RsToDic = dic
	End Function


	'ExecuteTemplate
	Private Function ExecuteTemplate(ByVal strHtml)
		Dim html : html = strHtml
		Dim Matchs
		Set Matchs = AB.C.RegMatch(html,"\{(?:if)\s+([^}]*?)?\}")
		If Matchs.Count Then
			html = AB.C.RegReplace(html,"\{(?:if)\s+([^}]*?)?\}","<"&"% If $1 Then %"&">")
			html = AB.C.RegReplace(html,"\{(?:elseif|ef)\s+([^}]*?)?\}","<"&"% ElseIf $1 Then %"&">")
			html = AB.C.RegReplace(html,"\{(?:else\s+if)\s+([^}]*?)?\}","<"&"% Else If $1 Then %"&">")
			html = AB.C.RegReplace(html,"\{else\s*\}","<"&"% Else %"&">")
			html = AB.C.RegReplace(html,"\{/if\}","<"&"% End If %"&">")
		End If
		'AB.C.Exec(html)
		Set Matchs = AB.C.RegMatch(html,"\<"&"%([\s\S]*?)%"&"\>")
		If Matchs.Count Then'ASP代码支持，还不是那么完美,如果要解决，就要在下面的代码里面做处理
		Dim tmp : tmp = ExpSplit(html,"\<"&"%([\s\S]*?)%"&"\>")
			Dim i
			Dim htm : htm = "Dim str : str = """"" & vbcrlf
			For i = 0 To UBound(tmp)
				If AB.C.Has(AB.C.RegReplace(tmp(i),"[\n\t\r|]|(\s+|&nbsp;|　)+", "")) Then
					tmp(i) = Replace(Replace(tmp(i),"<"&"%","&lt;%"),"%"&">","%&gt;")
					htm = htm & "str = str & tmp("&i&")" & vbcrlf
				End If
				If i <= (Matchs.Count - 1) Then htm = htm & Matchs(i).SubMatches(0) & vbcrlf
			Next
			AB.C.Exec(htm)
			html = str
		End If
		Set Matchs = Nothing
			ExecuteTemplate = html
	End Function

	
	'IF
	Private Function ReturnIfLabel(ByVal strHtml,ByVal strHead,ByVal dicRS)
		Dim html : html = strHtml
		Dim Match
		For Each Match in AB.C.RegMatch(strHtml,"\{(?:if|elseif|ef)\s+([^}]*?)?\}")
			html = Replace(html,Match.value,ReturnLabelValues(Match.value,strHead,dicRS,0))
		Next
		ReturnIfLabel = html
	End Function

	
	''标签属性值替换输出
	Private Function ReturnLabelValues(ByVal v,ByVal strHead,ByVal dicObj,ByVal key)
		Dim return,html : html = v
		Dim val,Match
		Dim Pattern(2)
			Pattern(0) = "\((?:" & ExpEncode(Lcase(strHead)) &"){1}([a-zA-Z0-9\/_]+)((?:\[@?(?:\w+=.*?)?\])?\.?(?:\w+)?(?:\:\w+)?)?(\s+[^)][\s\S]*?)?\s*\)"'()标签
			Pattern(1) = "\{(?:" & ExpEncode(Lcase(strHead)) &"){1}([a-zA-Z0-9\/_]+)((?:\[@?(?:\w+=.*?)?\])?\.?(?:\w+)?(?:\:\w+)?)?(\s+[^}][\s\S]*?)?\s*\}"'{}标签
			'(0)'标签名  (1)'路径  (2)'属性
		For Each Match in AB.C.RegMatch(v,Pattern(key))
			If Len(Match.SubMatches(1)) Then'如果是通过路径获取属性
				return = getAttr(Match.SubMatches(0)&Match.SubMatches(1))
			Else
				return = dicObj(Lcase(Match.SubMatches(0)))
			End If
			
			If Len(Match.SubMatches(2))>1 Then
				return = FormatValues(return,GetBlockAttr(Match.SubMatches(2)))
			End If
			
			html   = Replace(html,Match.Value,return,1,-1,0)

		Next
		ReturnLabelValues = html
	End Function

	
	'返回一个标签节点的信息
	Private Function GetLabelNode(ByVal node)
		Dim aryLabel(6)
			aryLabel(0) = node.nodeName '节点名称
		If node.childNodes.Length < 3 Then
			aryLabel(1) = node.childNodes(0).nodeTypedValue '0=strAttr
			aryLabel(2) = node.childNodes(1).nodeTypedValue '1=strHtml
		End If
		If node.childNodes.Length > 3 Then
			aryLabel(1) = node.childNodes(0).nodeTypedValue '0=strAttr
			aryLabel(2) = node.childNodes(1).nodeTypedValue '1=strHtml
			aryLabel(3) = node.childNodes(2).nodeTypedValue '2=strBody
			aryLabel(4) = node.childNodes(3).nodeTypedValue '3=strEmpty
		End If
		Set aryLabel(5) = GetBlockAttr(node)'标签节点的所有属性
		GetLabelNode = aryLabel
	End Function

	
	'创建一个模板节点
	Private Function GetTemplateNode(ByVal arrayTags)
		'XML操作部分
		Dim subNode0,subNode1,subNode2,subNode3,subNode4,subNode5
		Set subNode0 = TplXml.CreateElement(Lcase(Trim(arrayTags(1))))
		Set subNode1 = TplXml.CreateElement("attr") : subNode1.appendChild(TplXml.createCDATASection(arrayTags(2)))'标签属性
		Set subNode2 = TplXml.CreateElement("html") : subNode2.appendChild(TplXml.createCDATASection(arrayTags(7)))'模板内容
		Set subNode3 = TplXml.CreateElement("body") : subNode3.appendChild(TplXml.createCDATASection(arrayTags(5)))'循环体部分
		Set subNode4 = TplXml.CreateElement("null") : subNode4.appendChild(TplXml.createCDATASection(arrayTags(4)))'empty标签
		
		'设置节点的属性
		Dim keys,tagAttr
		Set tagAttr = GetBlockAttr(arrayTags(2))'提取属性部分，名=值

		'添加子节点
		subNode0.appendChild(subNode1)
		subNode0.appendChild(subNode2)
		
		If arrayTags(6) Then'如果是闭合标签
			subNode0.appendChild(subNode3)
			subNode0.appendChild(subNode4)
			subNode0.SetAttribute "nodepath" , arrayTags(8) '辅助路径属性

			If Len(arrayTags(2))>1 Then
				Dim strSql
					strSql = AB.C.RegReplace(tagAttr("sql"),"^(\w+)\(\s*(\w+)\s*,\s*(\w+)\s*\)$","$1(Tags,tagAttr)")'找SQL
					IF Right(strSql,14) = "(Tags,tagAttr)" Then
					strSql = Eval(strSql)
					End If
				subNode0.SetAttribute "sql" , strSql'SQL属性
			End If
			
			'递归调用，这里是实现嵌套循环的关键
			CompileTemplate Array(arrayTags(3),arrayTags(0)),subNode0 
		End If
		
		'添加属性到节点中
		For Each keys in tagAttr
			subNode0.SetAttribute keys,tagAttr(keys)
		Next
		
		Set GetTemplateNode = subNode0
	End Function

	
	'分离EMPTY和循环体(代码,标签头)
	Private Function GetCloseBlock(ByVal aryTags)
		Dim ary(1)
		If Len(aryTags(0))>0 Then
			Dim Match,strSubPattern
				strSubPattern = "\{((?:empty|null|eof|nodata)\:"&aryTags(1)&")\s*?(?:[\s\S.]*?)\/?\}(?:([\s\S.]*?)\{/\1\})"
			Set Match = AB.C.RegMatch(aryTags(0),strSubPattern)
			
			If Match.Count Then'如果有 empty 标签
				ary(0) = Match(0).SubMatches(1) 'empty标签
				ary(1) = AB.C.RegReplace(aryTags(0),strSubPattern,"") '循环体部分
			Else
				ary(1) = aryTags(0)
			End If
			Set Match = Nothing
		End If
		GetCloseBlock = ary
	End Function

	
	'获得属性列表,返回名值的字典对象
	Private Function GetBlockAttr(ByVal val)
		Dim i
		Dim Match,Matches,dicAttr
		Set dicAttr = Dicary()'定义字段对象
		'返回一个标签节点的所有属性
		If TypeName(val) = "IXMLDOMElement" Then
			For i = 0 To val.attributes.Length - 1
				dicAttr(val.attributes(i).nodeName) = val.attributes(i).nodeTypedValue
			Next
		Else'存储名值对象
			Set Matches = AB.C.RegMatch( val ,"([a-zA-Z0-9]+)\s*=\s*(['|""])([\s\S.]*?)\2")
			For Each Match in Matches'0=属性,2=属性值
				dicAttr(Lcase(Trim(Match.SubMatches(0)))) = Match.SubMatches(2)
			Next
			Set Matches = Nothing
		End If
		Set GetBlockAttr = dicAttr
	End Function

	
	'选择一个带路径的节点,返回解析分解后的路径
	Private Function SelectLabelNode(ByVal strPath)
		Dim Match,Matches : strPath = Lcase(Trim(strPath))'标签转换成小写
		Set Matches = AB.C.RegMatch( strPath ,"([a-zA-Z0-9\/]+)(\[@?((\w+)=(.*?))?\])?\.?(\w+)?(\:(body|empty|html|null|eof))?")
		'传入参数示例：tag[attr=2].attr2:body
		If Matches.Count Then
			Dim ary(5)
			ary(0) = Matches(0).SubMatches(0) 'tag
			ary(1) = Matches(0).SubMatches(2) 'attr=2
			ary(2) = Matches(0).SubMatches(5) 'attr2
			ary(3) = Matches(0).SubMatches(6) ':body|:empty|:html
				
			Dim nodesPath'指定辅助路径
			nodesPath = s_XmlRoot & "/" & ary(0)
			If Len(Matches(0).SubMatches(1))>4 Then
				nodesPath = nodesPath & "[@" &ary(1)& "]"
			End If
			
			ary(4) = nodesPath'选择的路径
			SelectLabelNode = ary
		Else
			SelectLabelNode = Null
		End If
		Set Matches = Nothing
	End Function

	
	'设置节点属性
	Private Function SetLabelAttr(ByVal strPath,ByVal v)
		Dim ary,node,i,ii
		strPath = Split(strPath,",")
		For ii = 0 To Ubound(strPath)
			ary = SelectLabelNode(strPath(ii))'选择标签节点
			If IsArray(ary) = False Then Exit Function
			Select Case Lcase(ary(3))
			Case ":body"
				Set node = TplXml.selectNodes(ary(4) & "/body")
					For i = 0 to node.Length - 1
						node(i).childNodes(0).nodeValue = v
					Next
			Case ":empty",":null",":eof"
				Set node = TplXml.selectNodes(ary(4) & "/null")
					For i = 0 to node.Length - 1
						node(i).childNodes(0).nodeValue = v
					Next
			Case ":html"
				Set node = TplXml.selectNodes(ary(4) & "/html")
					For i = 0 to node.Length - 1
						node(i).childNodes(0).nodeValue = v
					Next
			Case Else
				If Len(ary(2)) Then
					Set node = TplXml.selectNodes(ary(4))
					For i = 0 to node.Length - 1
					node(i).setAttribute ary(2),v
					Next
				End If
			End Select
		Next
	End Function


	'输出结果输出模板的绝对路径
	Private Function absPath(ByVal strCode)
		Dim html : html = strCode
		Dim Matches,Match
		Dim spath
			Set Matches = AB.C.RegMatch(html,"(?:href|src)=(['""|])(?!(\/|\{|\(|\.\/|http:\/\/|https:\/\/|javascript:|#))(.+?)\1")
			For Each Match in Matches
				html = Replace( html , Match.value , Replace(Match.value,Match.SubMatches(2), relPath(Match.SubMatches(2)) ,1,-1,0) ,1,-1,0)
			Next
			Set Matches = Nothing
			absPath = html
	End Function

	
	'替换相对路径，主要是模板资源路径要正确
	'根据模板路径把../逐层替换到对应的目录
	Private Function relPath(ByVal strPath)
		Dim tpldir,src,spath
		Dim Matches
			strPath= AB.C.RegReplace(strPath,"(\/|\\)+", "/")
			
			Set Matches = AB.C.RegMatch(strPath,"^(\.\.\/)+")
			If Matches.Count=0 Then
				relPath = AB.C.RegReplace(s_TplPath,"(\/|\\)+", "/") & strPath
				Set Matches = Nothing
				Exit Function
			End If
			
			'模板的全路径
			spath = Split(AB.C.RegReplace(s_TplPath,"(\/|\\)+", "/"),"/")
			
		Dim i,n
			n = AB.C.RegMatch(Matches(0).Value,"(\.\.\/)").Count'看有多少个../
			For i=0 To Ubound(spath) - 1 - n
				src = src & spath(i) & "/"
			Next
			'把../替换成正确的目录
			relPath = Replace(strPath,Matches(0).value,src ,1,-1,0)
			Set Matches = Nothing
	End Function

End Class
%>